//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: ucs4code.c 792 2007-06-26 08:07:28Z roger $
//

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <unihan_im.h>
#include <unihan_im_keycodes.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

typedef struct {
	char preedit[7];
	int len;
	int pos;
} UCS4CodeSessionData;

int unihan_im_module_init(void);
void unihan_im_module_done(void);
int unihan_im_module_get_info(UnihanIMModuleArgList *args);

static int ucs4code_init(UnihanIM *uim);
static int ucs4code_done(UnihanIM *uim);
static int ucs4code_create_session(UnihanIM *uim);
static int ucs4code_destroy_session(UnihanIM *uim);
static int ucs4code_handle_event(UnihanIM *uim, IMEvent *ev);
static int handle_key_event(UnihanIM *uim, IMKeyEvent *ev);
static int handle_focus_event(UnihanIM *uim, IMFocusEvent *ev);
static int handle_config_change_event(UnihanIM *uim, IMConfigChangeEvent *ev);
static int handle_switch_event(UnihanIM *uim, IMSwitchEvent *ev);
static void preedit_draw(UnihanIM *uim);
static unsigned char *ucs4_to_utf8(UnihanIM *uim, unsigned long int ucs4);


int
unihan_im_module_init(void)
{
#if DEBUG
	fprintf(stderr, "ucs4code: unihan_im_module_init()\n");
#endif

	/* The input module can do some initialization here */
	
	return TRUE;
}


void
unihan_im_module_done(void)
{
#if DEBUG
	fprintf(stderr, "ucs4code: unihan_im_module_done()\n");
#endif

	/* The input module can do some finalization here */
	
	return;
}


int
unihan_im_module_get_info(UnihanIMModuleArgList *args)
{
	IMMethods *imm = NULL;
	IMInfo *iminfo = NULL;
#if 0
	UnihanPropertyArgList new_property;
#endif

#if DEBUG
	fprintf(stderr, "ucs4code: unihan_im_module_get_info()\n");
#endif
	
	if (!args) return FALSE;
	while (args->id) {
		switch (args->id) {
			case UNIHAN_IM_API_VERSION:
				/* Unihan only supports API version 1.0 now */
				args->value = (void*)0x00010000;
				break;
			case UNIHAN_IM_MODULE_ID:
				/* The ID of the input module */
				args->value = strdup("ucs4code");
				break;
			case UNIHAN_IM_MODULE_NAME:
				/* Human readable name of the input module */
				args->value = strdup("Unicode");
				break;
			case UNIHAN_IM_MODULE_VERSION:
				/* Version of the input module */
				args->value = strdup("1.0");
				break;
			case UNIHAN_IM_MODULE_IM_LIST:
				/* List of input methods in this input
				** module */
				imm = (IMMethods *)malloc(sizeof(IMMethods));
				iminfo = (IMInfo *)malloc(sizeof(IMInfo));
				memset(imm, 0, sizeof(IMMethods));
				memset(iminfo, 0, sizeof(IMInfo));

				/* define callback functions */
				imm->init = ucs4code_init;
				imm->done = ucs4code_done;
				imm->create_session = ucs4code_create_session;
				imm->destroy_session = ucs4code_destroy_session;
				imm->handle_event = ucs4code_handle_event;
				/* other info of this input method */
				iminfo->id = strdup("ucs4code");
				iminfo->name = strdup("Unicode");
				iminfo->version = strdup("1.0");
				iminfo->imUserData = NULL;
				iminfo->methodTable = imm;
				
#if 0
				new_property = (UnihanPropertyArgList)malloc(sizeof(UnihanPropertyArgs));
				memset(new_property, 0, sizeof(UnihanPropertyArgs));
				new_property->type = UNIHAN_PROPERTY_NONE;
			
				iminfo->proplist = new_property;
#endif
				/* there is only 1 input method in this
				** module, so next is NULL */
				iminfo->next = NULL;

				args->value = iminfo;
				break;
		}
		++args;
	}

	return TRUE;
}


static int
ucs4code_init(UnihanIM *uim)
{
	char *locale = NULL;
	int err;
	
#if DEBUG
	fprintf(stderr, "ucs4code: ucs4code_init()\n");
#endif
	uim->desktopUserData = NULL;

	err = uim->m->locale_get(uim, &locale);
	if (err == 0)
#if DEBUG
		fprintf(stderr, "ucs4code: user's locale is %s\n", locale);
#endif

	return TRUE;
}


static int
ucs4code_done(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "ucs4code: ucs4code_done()\n");
#endif
	return TRUE;
}


static int
ucs4code_create_session(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "ucs4code: ucs4code_create_session()\n");
#endif
	uim->sessionUserData = malloc(sizeof(UCS4CodeSessionData));
	memset(uim->sessionUserData, 0, sizeof(UCS4CodeSessionData));
	return TRUE;
}


static int
ucs4code_destroy_session(UnihanIM *uim)
{
#if DEBUG
	fprintf(stderr, "ucs4code: ucs4code_destroy_session()\n");
#endif
	free(uim->sessionUserData);
	return TRUE;
}


static int
ucs4code_handle_event(UnihanIM *uim, IMEvent *ev)
{
#if DEBUG
	fprintf(stderr, "ucs4code: ucs4code_handle_event()\n");
#endif

	switch (ev->type) {
		case IM_KEY:
			return handle_key_event(uim, (IMKeyEvent*)ev);
		case IM_FOCUS:
			return handle_focus_event(uim, (IMFocusEvent*)ev);
		case IM_CONFIG_CHANGE:
			return handle_config_change_event(uim, (IMConfigChangeEvent*)ev);
		case IM_SWITCH:
			return handle_switch_event(uim, (IMSwitchEvent*)ev);
		case IM_NOTHING:
		default:
			break;
	}

	return FALSE;
}


static int
handle_key_event(UnihanIM *uim, IMKeyEvent *ev)
{
	//int err;
	UCS4CodeSessionData *s = (UCS4CodeSessionData *)uim->sessionUserData;

	if (ev->modifier == 0 && ((ev->keyChar >= 'a' && ev->keyChar <= 'f')
                        || (ev->keyChar >= '0' && ev->keyChar <= '9'))) {
		if (s->len < 6) {
			s->preedit[s->len++] = ev->keyChar;
                        s->preedit[s->len] = 0;
			preedit_draw(uim);
		} else {
                        uim->m->beep(uim);
                }
		return TRUE;
	} else if (ev->modifier == 0 && (ev->keyCode == IM_VK_ENTER
                                || ev->keyCode == IM_VK_SPACE)) {
                unsigned long int ucs4;
                char *nptr = NULL;
                
		if (s->len == 0)
			return FALSE;

                ucs4 = strtoul(s->preedit, &nptr, 16);
                if (nptr == s->preedit) {
                        // nothing was converted at all
                        uim->m->beep(uim);
                        return TRUE;
                }

                if (ucs4 > 0x10ffff) {
                        uim->m->beep(uim);
                        return TRUE;
                }

                // illegal characters
                if (ucs4 < 0x20 || (ucs4 >= 0xD800 && ucs4 <= 0xDFFF) ||
                                        ucs4 == 0xFFFE || ucs4 == 0xFFFF) {
                        uim->m->beep(uim);
                        return TRUE;
                }

                uim->m->commit_string(uim, ucs4_to_utf8(uim, ucs4));
                if (ucs4 == 0x28CD2) {
                        uim->m->commit_string(uim, "咩事啊！");
                }

                s->len = 0;
                memset(s->preedit, 0, 7);
                uim->m->preedit_erase(uim);
		return TRUE;
	} else if (ev->modifier == 0 && ev->keyCode == IM_VK_ESCAPE) {
		if (s->len == 0)
			return FALSE;

		s->len = 0;
		memset(s->preedit, 0, 7);
		uim->m->preedit_erase(uim);
		return TRUE;
	} else if (ev->modifier == 0 && ev->keyCode == IM_VK_BACK_SPACE) {
		if (s->len == 0)
			return FALSE;

		--s->len;
		s->preedit[s->len] = '\0';
		preedit_draw(uim);
		return TRUE;
	}

	return FALSE;
}


static int
handle_focus_event(UnihanIM *uim, IMFocusEvent *ev)
{
	if (ev->focused == TRUE) {
		/* the input area has received focus */
                preedit_draw(uim);
	} else {
		/* the input area lost focus */
                uim->m->preedit_erase(uim);
	}

        return TRUE;
}


static int
handle_config_change_event(UnihanIM *uim, IMConfigChangeEvent *ev)
{
#if DEBUG
	fprintf(stderr, "ucs4code: config key %s has changed\n", ev->key);
#endif
        return FALSE;
}


static int
handle_switch_event(UnihanIM *uim, IMSwitchEvent *ev)
{
	UCS4CodeSessionData *s = (UCS4CodeSessionData *)uim->sessionUserData;
	if (ev->on == TRUE) {
		/* the user has switched on this input method */
	} else {
		/* the user has switched off this input method */
	}
        s->len = 0;
        memset(s->preedit, 0, 7);
        uim->m->preedit_erase(uim);

        return TRUE;
}


static unsigned char *
ucs4_to_utf8(UnihanIM *uim, unsigned long int ucs4)
{
	int len = 0, byte1, i;
        unsigned char *utf8 = NULL;

	if (ucs4 < 0x80) {
		byte1 = 0;
		len = 1;
	} else if (ucs4 < 0x800) {
		byte1 = 0xC0;
		len = 2;
	} else if (ucs4 < 0x10000) {
		byte1 = 0xE0;
		len = 3;
	} else if (ucs4 < 0x200000) {
		byte1 = 0xF0;
		len = 4;
	} else if (ucs4 < 0x4000000) {
		byte1 = 0xF8;
		len = 5;
	} else {
		byte1 = 0xFC;
		len = 6;
	}

        uim->m->mem_alloc(uim, len + 1, (void **)&utf8);
	if (utf8) {
                memset(utf8, 0, len + 1);
		for (i = len - 1; i > 0; --i) {
			utf8[i] = (ucs4 & 0x3F) | 0x80;
			ucs4 >>= 6;
		}
		utf8[0] = ucs4 | byte1;
	}

#if 1
        fprintf(stderr, "converted from UCS-4 to UTF-8: ");
        for (i = 0; utf8[i]; ++i) {
                fprintf(stderr, "%02x ", utf8[i]);
        }
        fprintf(stderr, "\n");
#endif
        
	return utf8;
}


static void
preedit_draw(UnihanIM *uim)
{
        char pestr[9];
	UCS4CodeSessionData *s = (UCS4CodeSessionData *)uim->sessionUserData;

        if (s->len == 0) {
                uim->m->preedit_erase(uim);
                return;
        }

        memset(pestr, 0, 9);
        pestr[0] = 'U';
        pestr[1] = '+';
        strcpy(pestr+2, s->preedit);
        uim->m->preedit_draw(uim, pestr, NULL);
}

// vi:ts=4:nowrap:cin:expandtab
